#pragma once
#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cerrno>
#include <sys/wait.h>
#include <functional>
#include "protobuf.hpp"

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
        ACCEPT_ERR,
        RECV_ERR
    };
    // using func_t = std::function<bool(const HttpRequest &, const HttpResponse &)>;
    using func_t = std::function<bool(const HttpRequest &, HttpResponse &)>;
    static const uint16_t gport = 8080;
    static const int gbacklog = 5;
    class HttpServer
    {
    public:
        HttpServer(func_t func, const uint16_t &port = gport)
            : _func(func), _listenSock(-1), _port(port)
        {
        }

        void initServer()
        {
            _listenSock = socket(AF_INET, SOCK_STREAM, 0);
            // 创建套接字
            if (_listenSock < 0)
            {
                std::cout << "创建套接字失败" << std::endl;
                std::cout << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            // 绑定端口
            struct sockaddr_in local;
            socklen_t len = sizeof local;
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;
            if (bind(_listenSock, (struct sockaddr *)&local, len) < 0)
            {
                std::cout << "绑定套接字失败" << std::endl;
                std::cout << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            // 设置为监听状态 listen接口 ，另外我们在客户端的时候需要写唤醒connect的逻辑，但是我们把服务器作为客户端，我们就不用写connect了
            if (listen(_listenSock, gbacklog) < 0)
            {
                std::cout << "设置监听套接字失败" << std::endl;
                std::cout << strerror(errno) << std::endl;
                exit(LISTEN_ERR);
            }
        }

        void handlertHttp(int sock)
        {
            // 准备接受请求
            // resv read 函数
            // ssize_t recv(int sockfd, void *buf, size_t len, int flags);
            char buffer[4096];
            HttpRequest req;
            HttpResponse resp;
            size_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
            if (n <= 0)
            {
                // 无论是客户端关闭，还是出错了。我都退出
                std::cout << "接受套接字失败" << std::endl;
                std::cout << strerror(errno) << std::endl;
                exit(ACCEPT_ERR);
            }
            // 成功读取
            buffer[n] = 0;
            req.inbuffer = buffer;
            // 给其他参数赋值
            req.parse();
            // 执行我们的函数
            _func(req, resp);
            // ssize_t send(int sockfd, const void *buf, size_t len, int flags);
            send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);
        }

        void start()
        {
            // 第一件事，就是选择接受连接
            for (;;)
            {
                struct sockaddr_in peer;
                socklen_t len = sizeof peer;
                int sock = accept(_listenSock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    std::cout << "接受套接字失败" << std::endl;
                    std::cout << strerror(errno) << std::endl;
                    exit(ACCEPT_ERR);
                }

                // 接受请求成功

                pid_t pid = fork();
                if (pid == 0)
                {
                    // 子进程
                    //  关闭没用的描述符
                    close(_listenSock);
                    if (fork() != 0)
                    {
                        // 让子进程先退出，父进程能快速的回收资源
                        exit(0);
                    }
                    // 让孙子进程来执行代码的逻辑
                    handlertHttp(sock);
                    close(sock);
                    exit(0);
                }
                close(sock);
                // 父进程
                waitpid(pid, nullptr, 0);
            }
        }

        ~HttpServer()
        {
        }

    private:
        int _listenSock;
        uint16_t _port;
        func_t _func;
    };
}